Spring MVC 处理异步请求(下)- Spring 中的实例

Spring MVC 处理异步请求(下)- Spring 中的实例

本文承接上一篇对于 Callable 的详解,继续讲述 Spring 是如何使用 Callable,或者 DeferredResult 的返回结果,进行最终结果的处理的。因为上一篇已经叙述了 Callable 在线程池中的处理逻辑,本篇将直接基于 Spring MVC 的例子,来切实感受一下异步请求在实际应用中带来的便利。

使用 Callable 返回异步结果

首先,为了使我们的 MVC 工程可以接受异步请求的处理,我们需要在 web.xml 中为我们的 DispathcerServlet 加入以下的配置:

1
2
3
4
5
6
7
8
9
10
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>

最重要的是最后一行配置:true,使得我们的 DispatcherServlet 可以处理异步请求。

其次,我们创建两个接口,分别用于执行阻塞的请求和不会阻塞的异步请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Controller
public class DeferredController {

@Autowired
DeferredService deferredService;

@RequestMapping(value = "blocking-request.do", method = RequestMethod.GET)
public String blockingService() {
System.out.println("start blocking-request...");
String result = deferredService.execute();
System.out.println("result retrieved!");
return result;
}

@RequestMapping(value = "non-blocking-request.do", method = RequestMethod.GET)
public Callable<String> nonBlockingService() {
System.out.println("start blocking-request...");

Callable<String> result = deferredService::execute;
System.out.println("result retrieved!");
return result;
}

}

其中,blocking-request.do 将会阻塞,并且将会先执行 execute() 方法,然后再返回结果;而 non-blocking-request.do 将会直接返回后续的 println 中的内容,而在最后返回由其他线程处理的结果。

之后,我们需要创建一个 TaskService 抽象类,并且让我们的 DeferredService 类去实现。

1
2
3
public abstract class TaskService {
public abstract String execute();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class DeferredService extends TaskService{

@Override
public String execute() {
try {
Thread.sleep(5000);
System.out.println("task finished!");
return "hello";
} catch (InterruptedException ex) {
throw new RuntimeException();
}
}
}

可以看到,DeferredService 会让线程阻塞一段时间(5秒),之后才会返回定向到的 jsp 页面名字。如果是使用阻塞的请求去执行该 service,则会让这个接口等在那里,并且不能接收其他的请求;而通过 Callable 执行异步请求的做法,将把这个耗时的逻辑转移到其他的线程,从而使接口可以及时接收其他的请求。

之后,我们运行 web 工程,分别输入:

  • localhost:8080/blocking-request.do: 该请求将直接进入阻塞状态,会在 TaskService 中等待 5 秒后,再将视图名称返回,因此,我们会看到 “task finished” 夹在中间,如下图所示:

    blocking

  • localhost:8080/non-blocking-request.do: 该请求会将 Callable 对象中的逻辑放入其他线程处理,并且在 Controller 中表现为直接返回,并且在 Callable 有返回值时返回视图的结果。因此,”task finished” 会在最后,如下图所示:

    non-blocking

从上面我们看出,Spring 在处理 Callable 的对象时,会将 Callable 转入其他线程处理,并且在 Controller 中直接返回使其可以处理其他的请求。而在 Callable 有结果返回时,再返回该异步请求的结果。

使用 DeferredResult 返回异步结果

根据 Spring MVC 文档,另一个选择就是返回 DeferredResult 对象。DeferredResult 比 Callable 更为复杂,但是也比 Callable 更为灵活,其体现在 DeferredResult 的返回值可以由任何一个线程产生,包括不由 Spring 控制的线程(对于 Callable 来说,返回还是在 Spring 的 WebAsyncMannager 实例中执行)。因此,我们可以在代码的其他线程内设置 DeferredResult 实例的具体返回值而不是像 Callable 中需要确切的结果返回(即 return result)。我们也提供一个实例来看看 DeferredResult 在真实的代码中是如何操作的。

我们在 DeferredController 中新增一个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping(value = "deffered-result.do", method = RequestMethod.GET)
public DeferredResult<String> deferredResultService() {
System.out.println("start deferredResult request...");
DeferredResult<String> result = new DeferredResult<>();
ForkJoinPool.commonPool().submit(() -> {
System.out.println("start deferred result handling");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {}
System.out.println("finished deferred result handling");
result.setResult("hello");
});
System.out.println("request finished!");
return result;
}

这个接口将会向 ForkJoinPool (线程池的一个具体实现类) 提交一个 task,并且会在 sleep 过后去设置 DeferredResult 实例的结果。而这个 task 的处理被放入了其他线程,使得耗时的逻辑可以释放主线程资源,使得主线程可以接收新的请求。

之后,运行 web 工程,输入 http://localhost:8080/deffered-result.do,我们可以看到 console 输入的信息顺序如下:

deferredResult

显而易见,主线程已经完成 request 的处理,并且将 task 逻辑交给了其他线程,之后在其他线程处理完毕后,设置 DeferredResult 的结果。因此,我们可以看到 DeferredResult 的结果可以来自于其他线程。

小结

本章完成 Spring MVC 处理异步请求的实例代码讲解,让异步请求这个新特性对于刚刚接触 Spring 的我们来说,显得不再那么神秘。